<!-- PopupComponent.vue -->
<template>
  <Teleport to="body">
    <Transition name="popup-fade" @after-leave="handleAfterLeave">
      <div
        v-if="visible"
        class="popup-overlay"
        :class="{ 'popup-mask': showMask }"
        @click="handleMaskClick"
      >
        <div
          ref="popupRef"
          class="popup-container"
          :class="[
            `popup--${placement}`,
            customClass,
            {
              'popup-shadow': shadow,
              'popup-border': border
            }
          ]"
          :style="computedStyle"
          @click.stop
        >
          <!-- 箭头 -->
          <div
            v-if="showArrow"
            class="popup-arrow"
            :style="arrowStyle"
          />
          
          <!-- 头部 -->
          <div v-if="$slots.header || title" class="popup-header">
            <slot name="header">
              <span class="popup-title">{{ title }}</span>
              <button
                v-if="closable"
                class="popup-close"
                @click="handleClose"
              >
                <svg viewBox="0 0 24 24" width="16" height="16">
                  <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
                </svg>
              </button>
            </slot>
          </div>

          <!-- 内容 -->
          <div class="popup-content">
            <slot />
          </div>

          <!-- 底部 -->
          <div v-if="$slots.footer" class="popup-footer">
            <slot name="footer" />
          </div>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted, onUnmounted, useSlots } from 'vue'

type Placement = 
  | 'top' | 'top-start' | 'top-end'
  | 'bottom' | 'bottom-start' | 'bottom-end'
  | 'left' | 'left-start' | 'left-end'
  | 'right' | 'right-start' | 'right-end'

interface PopupRect {
  top: number
  left: number
  width: number
  height: number
}

interface Props {
  modelValue: boolean
  title?: string
  placement?: Placement
  offset?: number | [number, number]
  showMask?: boolean
  maskClosable?: boolean
  showArrow?: boolean
  closable?: boolean
  shadow?: boolean
  border?: boolean
  customClass?: string
  teleport?: string | HTMLElement
  disabledTeleport?: boolean
  autoPlacement?: boolean
  maxWidth?: number | string
  maxHeight?: number | string
  minWidth?: number | string
  minHeight?: number | string
  zIndex?: number
}

const props = withDefaults(defineProps<Props>(), {
  placement: 'bottom',
  offset: 8,
  showMask: true,
  maskClosable: true,
  showArrow: true,
  closable: true,
  shadow: true,
  border: false,
  teleport: 'body',
  autoPlacement: true,
  zIndex: 2000
})

const emit = defineEmits<{
  'update:modelValue': [visible: boolean]
  'open': []
  'close': []
  'placement-change': [placement: Placement]
}>()

const visible = ref(false)
const popupRef = ref<HTMLElement>()
const arrowStyle = ref({})
const popupRect = ref<PopupRect>({ top: 0, left: 0, width: 0, height: 0 })
const targetRect = ref<DOMRect | null>(null)
const currentPlacement = ref(props.placement)

// 计算偏移量
const offsetValue = computed(() => {
  if (Array.isArray(props.offset)) {
    return props.offset
  }
  return [0, props.offset]
})

// 计算样式
const computedStyle = computed(() => {
  const { top, left } = popupRect.value
  const style: Record<string, any> = {
    zIndex: props.zIndex,
    transform: 'translate3d(0, 0, 0)'
  }

  if (top !== undefined) style.top = `${top}px`
  if (left !== undefined) style.left = `${left}px`

  if (props.maxWidth) style.maxWidth = typeof props.maxWidth === 'number' ? `${props.maxWidth}px` : props.maxWidth
  if (props.maxHeight) style.maxHeight = typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight
  if (props.minWidth) style.minWidth = typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth
  if (props.minHeight) style.minHeight = typeof props.minHeight === 'number' ? `${props.minHeight}px` : props.minHeight

  return style
})

// 获取目标元素
const getTargetElement = (): HTMLElement | null => {
  // 尝试从触发事件中获取目标元素
  const { triggerEvent } = useTrigger()
  if (triggerEvent?.value) {
    const target = triggerEvent.value.currentTarget as HTMLElement
    if (target) return target
  }
  
  // 如果没有触发事件，尝试从 slot 获取
  const slots = useSlots()
  if (slots.reference) {
    // 这里需要用户手动绑定 reference
    return document.querySelector('[data-popup-reference]') as HTMLElement
  }
  
  return null
}

// 计算位置
const calculatePosition = async () => {
  if (!popupRef.value) return

  const target = getTargetElement()
  if (!target) {
    console.warn('Popup: 未找到目标元素')
    return
  }

  await nextTick()
  
  const popupEl = popupRef.value
  const targetRectVal = target.getBoundingClientRect()
  const popupRectVal = popupEl.getBoundingClientRect()
  
  targetRect.value = targetRectVal

  let placement = currentPlacement.value
  const [offsetX, offsetY] = offsetValue.value

  // 计算基础位置
  let top = 0
  let left = 0

  switch (placement) {
    case 'top':
      top = targetRectVal.top - popupRectVal.height - offsetY
      left = targetRectVal.left + (targetRectVal.width - popupRectVal.width) / 2
      break
    case 'top-start':
      top = targetRectVal.top - popupRectVal.height - offsetY
      left = targetRectVal.left
      break
    case 'top-end':
      top = targetRectVal.top - popupRectVal.height - offsetY
      left = targetRectVal.right - popupRectVal.width
      break
    case 'bottom':
      top = targetRectVal.bottom + offsetY
      left = targetRectVal.left + (targetRectVal.width - popupRectVal.width) / 2
      break
    case 'bottom-start':
      top = targetRectVal.bottom + offsetY
      left = targetRectVal.left
      break
    case 'bottom-end':
      top = targetRectVal.bottom + offsetY
      left = targetRectVal.right - popupRectVal.width
      break
    case 'left':
      top = targetRectVal.top + (targetRectVal.height - popupRectVal.height) / 2
      left = targetRectVal.left - popupRectVal.width - offsetX
      break
    case 'left-start':
      top = targetRectVal.top
      left = targetRectVal.left - popupRectVal.width - offsetX
      break
    case 'left-end':
      top = targetRectVal.bottom - popupRectVal.height
      left = targetRectVal.left - popupRectVal.width - offsetX
      break
    case 'right':
      top = targetRectVal.top + (targetRectVal.height - popupRectVal.height) / 2
      left = targetRectVal.right + offsetX
      break
    case 'right-start':
      top = targetRectVal.top
      left = targetRectVal.right + offsetX
      break
    case 'right-end':
      top = targetRectVal.bottom - popupRectVal.height
      left = targetRectVal.right + offsetX
      break
  }

  // 自动调整位置（防止溢出）
  if (props.autoPlacement) {
    const viewport = {
      width: window.innerWidth,
      height: window.innerHeight
    }

    // 检查水平溢出
    if (left < 0) {
      left = 10
    } else if (left + popupRectVal.width > viewport.width) {
      left = viewport.width - popupRectVal.width - 10
    }

    // 检查垂直溢出
    if (top < 0) {
      // 如果上方空间不足，尝试下方
      if (placement.startsWith('top')) {
        placement = placement.replace('top', 'bottom') as Placement
        top = targetRectVal.bottom + offsetY
      } else {
        top = 10
      }
    } else if (top + popupRectVal.height > viewport.height) {
      // 如果下方空间不足，尝试上方
      if (placement.startsWith('bottom')) {
        placement = placement.replace('bottom', 'top') as Placement
        top = targetRectVal.top - popupRectVal.height - offsetY
      } else {
        top = viewport.height - popupRectVal.height - 10
      }
    }

    if (placement !== currentPlacement.value) {
      currentPlacement.value = placement
      emit('placement-change', placement)
    }
  }

  // 计算箭头位置
  if (props.showArrow) {
    calculateArrowPosition(placement, targetRectVal)
  }

  popupRect.value = { top, left, width: popupRectVal.width, height: popupRectVal.height }
}

// 计算箭头位置
const calculateArrowPosition = (placement: Placement, targetRect: DOMRect) => {
  const arrowSize = 6
  const style: Record<string, any> = {}

  switch (placement) {
    case 'top':
    case 'top-start':
    case 'top-end':
      style.bottom = '-6px'
      style.left = `${targetRect.left + targetRect.width / 2 - popupRect.value.left - arrowSize}px`
      break
    case 'bottom':
    case 'bottom-start':
    case 'bottom-end':
      style.top = '-6px'
      style.left = `${targetRect.left + targetRect.width / 2 - popupRect.value.left - arrowSize}px`
      break
    case 'left':
    case 'left-start':
    case 'left-end':
      style.right = '-6px'
      style.top = `${targetRect.top + targetRect.height / 2 - popupRect.value.top - arrowSize}px`
      break
    case 'right':
    case 'right-start':
    case 'right-end':
      style.left = '-6px'
      style.top = `${targetRect.top + targetRect.height / 2 - popupRect.value.top - arrowSize}px`
      break
  }

  arrowStyle.value = style
}

// 打开弹窗
const open = async () => {
  visible.value = true
  emit('update:modelValue', true)
  await nextTick()
  await calculatePosition()
  emit('open')
}

// 关闭弹窗
const close = () => {
  visible.value = false
  emit('update:modelValue', false)
  emit('close')
}

// 处理遮罩点击
const handleMaskClick = () => {
  if (props.maskClosable) {
    close()
  }
}

// 处理关闭
const handleClose = () => {
  close()
}

// 处理动画结束
const handleAfterLeave = () => {
  // 清理状态
  arrowStyle.value = {}
}

// 监听 v-model 变化
watch(() => props.modelValue, (val) => {
  if (val !== visible.value) {
    if (val) {
      open()
    } else {
      close()
    }
  }
})

// 监听窗口大小变化
const handleResize = () => {
  if (visible.value) {
    calculatePosition()
  }
}

// 监听滚动
const handleScroll = () => {
  if (visible.value) {
    calculatePosition()
  }
}

onMounted(() => {
  if (props.modelValue) {
    open()
  }
  
  window.addEventListener('resize', handleResize)
  window.addEventListener('scroll', handleScroll, true)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
  window.removeEventListener('scroll', handleScroll, true)
})

// 触发器相关
const triggerEvent = ref<Event | null>(null)

const useTrigger = () => {
  return { triggerEvent }
}

// 暴露方法
defineExpose({
  open,
  close,
  calculatePosition
})
</script>

<style scoped lang="scss">
.popup-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.popup-mask {
  background: rgba(0, 0, 0, 0.5);
  pointer-events: auto;
}

.popup-container {
  position: absolute;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  max-width: calc(100vw - 20px);
  max-height: calc(100vh - 20px);
  overflow: hidden;
  pointer-events: auto;
}

.popup-border {
  border: 1px solid #e8e8e8;
}

.popup-shadow {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.popup-arrow {
  position: absolute;
  width: 0;
  height: 0;
  border: 6px solid transparent;
}

.popup-arrow::after {
  content: '';
  position: absolute;
  width: 0;
  height: 0;
  border: 6px solid transparent;
}

/* 箭头方向 */
.popup--top .popup-arrow,
.popup--top-start .popup-arrow,
.popup--top-end .popup-arrow {
  border-top-color: #fff;
  top: 100%;
}

.popup--bottom .popup-arrow,
.popup--bottom-start .popup-arrow,
.popup--bottom-end .popup-arrow {
  border-bottom-color: #fff;
  bottom: 100%;
}

.popup--left .popup-arrow,
.popup--left-start .popup-arrow,
.popup--left-end .popup-arrow {
  border-left-color: #fff;
  left: 100%;
}

.popup--right .popup-arrow,
.popup--right-start .popup-arrow,
.popup--right-end .popup-arrow {
  border-right-color: #fff;
  right: 100%;
}

.popup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid #f0f0f0;
}

.popup-title {
  font-size: 16px;
  font-weight: 500;
  color: #333;
}

.popup-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  color: #999;
  transition: color 0.3s;
  
  &:hover {
    color: #333;
  }
  
  svg {
    fill: currentColor;
  }
}

.popup-content {
  padding: 16px;
  overflow-y: auto;
  max-height: calc(100vh - 200px);
}

.popup-footer {
  padding: 12px 16px;
  border-top: 1px solid #f0f0f0;
  text-align: right;
}

/* 动画 */
.popup-fade-enter-active,
.popup-fade-leave-active {
  transition: opacity 0.3s ease;
}

.popup-fade-enter-from,
.popup-fade-leave-to {
  opacity: 0;
}

.popup-fade-enter-active .popup-container,
.popup-fade-leave-active .popup-container {
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.popup-fade-enter-from .popup-container {
  transform: scale(0.9);
  opacity: 0;
}

.popup-fade-leave-to .popup-container {
  transform: scale(0.9);
  opacity: 0;
}

/* 响应式 */
@media (max-width: 768px) {
  .popup-container {
    max-width: calc(100vw - 16px);
    max-height: calc(100vh - 16px);
  }
  
  .popup-content {
    max-height: calc(100vh - 150px);
  }
}
</style>